/***************************************************************************
 *   Copyleft 2006-2007 by												   *
 *	 Iwanicki Krzysztof													   *
 *   <gamma.mini.pw.edu.pl/~iwanickik>			                           *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 ***************************************************************************/
#include "./MainWindow.h"
#include "../Framework/Input/TorusManager.h"
#include "../Framework/Input/EllipsoidManager.h"
#include "../Framework/Input/PointsSetManager.h"
#include "../Framework/Input/BezierCurveManager.h"
#include "../Framework/Input/BSplineInterpolationManager.h"
#include "../Framework/Input/BSplineCurveManager.h"
#include "../Framework/Input/BSplineSurfaceManager.h"
#include "../Framework/Input/BezierSurfaceManager.h"
#include "../Framework/Input/GregoryManager.h"
#include "../Framework/Objects/Axis/XAxis.h"
#include "../Framework/Objects/Axis/YAxis.h"
#include "../Framework/Objects/Axis/ZAxis.h"
#include "../Framework/Objects/Torus.h"
#include "../Framework/Objects/Cuboid.h"
#include "../Framework/Objects/Ellipsoid.h"
#include "../Framework/Objects/Cursor3D/Cursor3D.h"
#include "../Framework/Objects/PointsSet.h"
#include "../Framework/Objects/Curves/BezierCurve.h"
#include "../Framework/Objects/Curves/BSplineInterpolation.h"
#include "../Framework/Objects/Surfaces/BezierSurface.h"
#include "../Framework/Objects/Surfaces/BSplineSurface.h"
#include "../Framework/Objects/Surfaces/GregorySurface.h"
#include "../Framework/Objects/Surfaces/TriGregory.h"
#include "./Dialog/AboutDialog.h"


MainWindow::MainWindow(HINSTANCE hInstance):AbstractWindow(hInstance, WINDOW_NAME, WINDOW_TITLE, cNull) {
	//mStatusBar = new StatusBar(hInstance, mhWnd, L"Welcome to CAD/CAM system");
	mEngine->mScene->AddObject(new XAxis(50.0f));
	mEngine->mScene->AddObject(new YAxis(50.0f));
	mEngine->mScene->AddObject(new ZAxis(50.0f));
	::SetMenu(mhWnd, ::LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1)));
}

cVoid MainWindow::Render() {
	this->mEngine->Refresh();
}

/*virtual*/ LRESULT CALLBACK MainWindow::WndProc(HWND hwnd, cUInt message, WPARAM wParam, LPARAM lParam) {
	switch (message) {
	WNDMSGHANDLER(WM_COMMAND, OnCommand);
	WNDMSGHANDLER(WM_MOUSEMOVE, OnMouseMove);
	WNDMSGHANDLER(WM_MOUSEWHEEL, OnMouseWheel);
	WNDMSGHANDLER(WM_LBUTTONDOWN, OnLeftMouseDown);
	WNDMSGHANDLER(WM_RBUTTONDOWN, OnRightMouseDown);
	WNDMSGHANDLER(WM_LBUTTONUP, OnLeftMouseUp);
	WNDMSGHANDLER(WM_RBUTTONUP, OnRightMouseUp);
	WNDMSGHANDLER(WM_KEYUP, OnKeyUp);
	WNDMSGHANDLER(WM_KEYDOWN, OnKeyDown);
	case WM_SIZING:
	case WM_SIZE:
		this->mEngine->Resize();
		break;
	default:
		return ::DefWindowProc(hwnd, message, wParam, lParam);
	}
	return 0;
}

cVoid MainWindow::OnCommand(HWND hwnd, WPARAM wParam, LPARAM lParam) {
	InputManager* manager;
	cUShort cmd = LOWORD(wParam);
	
	switch (cmd) {
	case ID_DISPLAYHELP:
		mEngine->SwitchDisplayHelp();
		::CheckMenuItem(::GetMenu(mhWnd), ID_DISPLAYHELP, 
			mEngine->GetDisplayHelp() ? MF_CHECKED : MF_UNCHECKED);
		break;
	case ID_FILE_EXIT:
		::PostQuitMessage(0);
		break;
	case ID_VIEW_STEREO:
		mEngine->SwitchStereoMonoView();
		break;
	case ID_CHANGEEYEDISTANCEINC:
		mEngine->ChangeEyeDistance(1.0f);
		break;
	case ID_CHANGEEYEDISTANCEDEC:
		mEngine->ChangeEyeDistance(-1.0f);
		break;
	case ID_HELP_ABOUT: 
		{ AboutDialog aboutDialog(mhInstance, mhWnd); }
		break;
	case ID_VIEW_FRONTVIEW:
		mEngine->mScene->Reset();
		break;
	case ID_FILE_SAVESCENE:
		SaveScene();
		break;
	case ID_FILE_LOADSCENE:
		LoadScene();
		break;
	default:
		break;
	}
	OnChangeEyeColor(cmd);
	OnShowHideAxis(cmd);
	OnAddObject(cmd);
	OnDeleteObject(cmd);
	OnChangeControl(cmd);
	if ((manager = mEngine->GetInputManager())) {
		manager->OnCommand(hwnd, wParam, lParam, mEngine);
	}
}

cVoid MainWindow::OnAddObject(cUShort cmd) {
	Object3D* object = cNull;

	switch (cmd) {
	case ID_ADDOBJECT_TORUS:
		object = new Torus(40.0f, 90.0f, 20, 10);
		mEngine->SetInputManager(new TorusManager((Torus*) object));
		mEngine->mScene->AddObject(object);
		OnAddObjectMenu(cmd, object);
		return;
	case ID_ADDOBJECT_ELLIPSOID:
		object = new Ellipsoid(10000.f, 6000.f, 3000.f, mEngine->GetRenderer());
		mEngine->SetInputManager(new EllipsoidManager((Ellipsoid*) object));
		mEngine->mScene->AddObject(object);
		OnAddObjectMenu(cmd, object);
		return;
	case ID_ADDOBJECT_POINTSET: {
			Cursor3D *cursor = new Cursor3D();
			object = new PointsSet();
			mEngine->mScene->AddCursor(cursor);
			mEngine->mScene->AddObject(object);
			mEngine->SetInputManager(new PointsSetManager(cursor, (PointsSet* ) object));
			OnAddObjectMenu(cmd, object);
		}
		break;
	case ID_ADDOBJECT_BEZIERCURVE: {
			Cursor3D *cursor = new Cursor3D();
			object = new BezierCurve();
			mEngine->mScene->AddCursor(cursor);
			mEngine->mScene->AddObject(object);
			mEngine->SetInputManager(new BezierCurveManager(cursor, (BezierCurve* ) object));
			OnAddObjectMenu(cmd, object);
		}
		break;
	case ID_ADDOBJECT_B: {
			Cursor3D *cursor = new Cursor3D();
			object = new BSplineInterpolation();
			mEngine->mScene->AddCursor(cursor);
			mEngine->mScene->AddObject(object);
			mEngine->SetInputManager(new BSplineInterpolationManager(
				cursor, (BSplineInterpolation* ) object));
			OnAddObjectMenu(cmd, object);
		}
		break;
	case ID_ADDOBJECT_BEZIERSURFACE: {
			Cursor3D *cursor = new Cursor3D();
			BezierCurve* bezier1 = new BezierCurve(), *bezier2 = new BezierCurve();
			object = new BezierSurface();
			mEngine->mScene->AddCursor(cursor);
			mEngine->mScene->AddObject(object);
			mEngine->mScene->AddObject(bezier1);
			mEngine->mScene->AddObject(bezier2);
			mEngine->SetInputManager(new BezierSurfaceManager(bezier1, bezier2,
				(BezierSurface* ) object, cursor));
			OnAddObjectMenu(cmd, object);
		}
		break;
	case ID_ADDOBJECT_B40099: {
			Cursor3D *cursor = new Cursor3D();
			object = new BSplineCurve();
			mEngine->mScene->AddCursor(cursor);
			mEngine->mScene->AddObject(object);
			mEngine->SetInputManager(new BSplineCurveManager(cursor, (BSplineCurve* ) object));
			OnAddObjectMenu(cmd, object);
		}
		break;
	case ID_ADDOBJECT_B40100: {
			Cursor3D *cursor = new Cursor3D();
			BSplineCurve* bSpline1 = new BSplineCurve(), *bSpline2 = new BSplineCurve();
			object = new BSplineSurface();
			mEngine->mScene->AddCursor(cursor);
			mEngine->mScene->AddObject(object);
			mEngine->mScene->AddObject(bSpline1);
			mEngine->mScene->AddObject(bSpline2);
			mEngine->SetInputManager(new BSplineSurfaceManager(bSpline1, bSpline2,
				(BSplineSurface* ) object, cursor));
			OnAddObjectMenu(cmd, object);
		}
		break;
	case ID_ADDOBJECT_FILLTRIANGULARHOLE: {
			Cursor3D *cursor = new Cursor3D();
			mEngine->mScene->AddCursor(cursor);
			mEngine->SetInputManager(new GregoryManager(cursor, mEngine));
			//OnAddObjectMenu(cmd, object);
		}
		break;
	default:
		return;
	}
}

cVoid MainWindow::OnAddObjectMenu(cUShort cmd, Object3D* object) {
	cChar buff[255];

	::EnableMenuItem(::GetMenu(mhWnd), ID_SCENE_REMOVEOBJECT, MF_ENABLED|MF_POPUP);
	cUInt id = object->GetID();
	sprintf_s(buff, "[%i] %s", id, object->GetName());

	HMENU objectsMenu = ::GetSubMenu(::GetMenu(mhWnd), 3);
	HMENU removeMenu = ::GetSubMenu(::GetSubMenu(::GetMenu(mhWnd), 2), 5);
	if (objectsMenu) {
		for (cInt i = 0; i < mEngine->mScene->GetNrOfObjects(); i++) {
			Object3D* obj = mEngine->mScene->GetObject(i + 3);
			if (obj) {
				::CheckMenuItem(::GetMenu(mhWnd), ID_OBJECT_START + obj->GetID(), MF_UNCHECKED);
			}
		}
		::AppendMenuA(objectsMenu, MF_CHECKED | MF_STRING, ID_OBJECT_START + id, buff);
		::RemoveMenu(objectsMenu, ID_OBJECTS40038, MF_BYCOMMAND);
	}
	if (removeMenu) {
		::AppendMenuA(removeMenu, MF_STRING, ID_REMOVE_OBJECT_START + id, buff);
		::RemoveMenu(removeMenu, ID_SCENE_REMOVEOBJECT40039, MF_BYCOMMAND);
	}
	::DrawMenuBar(mhWnd);
}

cVoid MainWindow::OnDeleteObject(cUShort cmd) {
	if (cmd > ID_REMOVE_OBJECT_START && cmd <= (ID_REMOVE_OBJECT_START + Object3D::NrOfAllCreatedObjects())) {
		cInt idToRemove = cmd - ID_REMOVE_OBJECT_START;
		HMENU objectsMenu = ::GetSubMenu(::GetMenu(mhWnd), 3);
		HMENU removeMenu = ::GetSubMenu(::GetSubMenu(::GetMenu(mhWnd), 2), 5);

		for (cInt i = 0; i < mEngine->mScene->GetNrOfObjects(); i++) {
			Object3D* obj = mEngine->mScene->GetObject(i + 3);
			if (obj && idToRemove == obj->GetID()) {
				if (mEngine->mScene->GetNrOfObjects() <= 1) {
					::AppendMenuA(objectsMenu, MF_STRING, ID_OBJECTS40038, "[Empty]");
					::AppendMenuA(removeMenu, MF_STRING, ID_SCENE_REMOVEOBJECT40039, "[Empty]");
				}
				::RemoveMenu(removeMenu, cmd, MF_BYCOMMAND);
				::RemoveMenu(objectsMenu, ID_OBJECT_START + obj->GetID(), MF_BYCOMMAND);
				mEngine->mScene->RemoveObject(obj);
				mEngine->ResetInputManager();
				mEngine->GetRenderer().ResetAdaptiveStep();
				return;
			}
		}
	}
}

cVoid MainWindow::OnChangeControl(cUShort cmd) {
	if (cmd > ID_OBJECT_START && cmd <= (ID_OBJECT_START + Object3D::NrOfAllCreatedObjects())) {
		Object3D *fObject = cNull;
		cInt idToOperate = cmd - ID_OBJECT_START;
		for (cInt i = 0; i < mEngine->mScene->GetNrOfObjects(); i++) {
			Object3D* obj = mEngine->mScene->GetObject(i + 3);
			if (obj) {
				::CheckMenuItem(::GetMenu(mhWnd), ID_OBJECT_START + obj->GetID(), MF_UNCHECKED);
				if (obj->GetID() == idToOperate) {
					fObject = obj;
				}
			}
		}
		::CheckMenuItem(::GetMenu(mhWnd), cmd, MF_CHECKED);
		if (fObject) {
			if (fObject->GetName() == "Torus") {
				mEngine->SetInputManager(new TorusManager((Torus*) fObject));
			} else if (fObject->GetName() == "Ellipsoid") {
				mEngine->SetInputManager(new EllipsoidManager((Ellipsoid*) fObject));
			} else if (fObject->GetName() == "BezierCurve") {
				if (mEngine->mScene->GetCursor()) {
					mEngine->mScene->AddCursor(new Cursor3D());
				}
				mEngine->SetInputManager(new BezierCurveManager(mEngine->mScene->GetCursor(), 
						(BezierCurve*) fObject));
			} else if (fObject->GetName() == "BSplineInterpolation") {
				if (mEngine->mScene->GetCursor()) {
					mEngine->mScene->AddCursor(new Cursor3D());
				}
				mEngine->SetInputManager(new BSplineInterpolationManager(mEngine->mScene->GetCursor(), 
						(BSplineInterpolation*) fObject));
			} else if (fObject->GetName() == "BezierSurface") {
				if (mEngine->mScene->GetCursor()) {
					mEngine->mScene->AddCursor(new Cursor3D());
				}
				mEngine->SetInputManager(new BezierSurfaceManager((BezierSurface*) fObject, 
						mEngine->mScene->GetCursor()));
			} else if (fObject->GetName() == "BSplineSurface") {
				if (!mEngine->mScene->GetCursor()) {
					mEngine->mScene->AddCursor(new Cursor3D());
				} 
				mEngine->SetInputManager(new BSplineSurfaceManager((BSplineSurface*) fObject, 
						mEngine->mScene->GetCursor()));
			} else if (fObject->GetName() == "BSplineCurve") {
				if (mEngine->mScene->GetCursor()) {
					mEngine->mScene->AddCursor(new Cursor3D());
				}
				mEngine->SetInputManager(new BSplineCurveManager(mEngine->mScene->GetCursor(), 
						(BSplineCurve*) fObject));
			} else if (fObject->GetName() == "PointsSet") {
				if (mEngine->mScene->GetCursor()) {
					mEngine->mScene->AddCursor(new Cursor3D());
				}
				mEngine->SetInputManager(new PointsSetManager(mEngine->mScene->GetCursor(), 
						(PointsSet*) fObject));
			}
		}
	}
}

cVoid MainWindow::OnShowHideAxis(cUShort cmd) {
	Object3D* object = cNull;
	
	switch (cmd) {
		case ID_SCENE_XAXIS:
		case ID_SCENE_YAXIS:
		case ID_SCENE_ZAXIS:
			if ((object = mEngine->mScene->GetObject(cmd - 40033))) {
				object->SetVisible();
				::CheckMenuItem(::GetMenu(mhWnd), cmd, 
					(object->GetVisible()) ? MF_CHECKED : MF_UNCHECKED);
			}
			return;
		default:
			return;
	}
}

cVoid MainWindow::OnChangeEyeColor(cUShort cmd) {
	if (cmd >= ID_LEFTEYECOLOR_RED && cmd <= ID_RIGHTEYECOLOR_BLUE) {
		if (cmd >= ID_LEFTEYECOLOR_RED && cmd <= ID_LEFTEYECOLOR_BLUE) {
			::CheckMenuItem(::GetMenu(mhWnd), ID_LEFTEYECOLOR_RED, MF_UNCHECKED);
			::CheckMenuItem(::GetMenu(mhWnd), ID_LEFTEYECOLOR_RED + 1, MF_UNCHECKED);
			::CheckMenuItem(::GetMenu(mhWnd), ID_LEFTEYECOLOR_RED + 1, MF_UNCHECKED);
			::CheckMenuItem(::GetMenu(mhWnd), cmd, MF_CHECKED);
		} else if (cmd >= ID_RIGHTEYECOLOR_RED && cmd <= ID_RIGHTEYECOLOR_BLUE) {
			::CheckMenuItem(::GetMenu(mhWnd), ID_RIGHTEYECOLOR_RED, MF_UNCHECKED);
			::CheckMenuItem(::GetMenu(mhWnd), ID_RIGHTEYECOLOR_RED + 1, MF_UNCHECKED);
			::CheckMenuItem(::GetMenu(mhWnd), ID_RIGHTEYECOLOR_RED + 2, MF_UNCHECKED);
			::CheckMenuItem(::GetMenu(mhWnd), cmd, MF_CHECKED);
		}
		switch(cmd) {
			case ID_LEFTEYECOLOR_RED:
				mEngine->SetLeftEyeColor(Color::ToWinColor(RED));
				break;
			case ID_LEFTEYECOLOR_GREEN:
				mEngine->SetLeftEyeColor(Color::ToWinColor(GREEN));
				break;
			case ID_LEFTEYECOLOR_BLUE:
				mEngine->SetLeftEyeColor(Color::ToWinColor(BLUE));
				break;
			case ID_RIGHTEYECOLOR_RED:
				mEngine->SetRightEyeColor(Color::ToWinColor(RED));
				break;
			case ID_RIGHTEYECOLOR_GREEN:
				mEngine->SetRightEyeColor(Color::ToWinColor(GREEN));
				break;
			case ID_RIGHTEYECOLOR_BLUE:
				mEngine->SetRightEyeColor(Color::ToWinColor(BLUE));
				break;
			default:
				return;
		}
	}
}

cVoid MainWindow::OnMouseMove(HWND hwnd, WPARAM wParam, LPARAM lParam) {
	InputManager* manager = mEngine->GetInputManager();

	if (manager) {
		cPoint q;

		q.x = ((cShort) LOWORD (lParam)) - (mEngine->GetWidth() >> 1);
		q.y = ((cShort) HIWORD (lParam)) - (mEngine->GetHeight() >> 1);
		manager->OnMouseMove(q, mEngine);
	}
}

cVoid MainWindow::OnMouseWheel(HWND hwnd, WPARAM wParam, LPARAM lParam) {
	InputManager* manager = mEngine->GetInputManager();

	if (manager) {
		manager->OnMouseWheel(GET_WHEEL_DELTA_WPARAM(wParam), mEngine);
	}
}

cVoid MainWindow::OnLeftMouseDown(HWND hwnd, WPARAM wParam, LPARAM lParam) {
	InputManager* manager = mEngine->GetInputManager();
	::SetCapture(hwnd);

	if (manager) {
		cPoint q;

		q.x = ((cShort) LOWORD (lParam)) - (mEngine->GetWidth() >> 1);
		q.y = ((cShort) HIWORD (lParam)) - (mEngine->GetHeight() >> 1);
		manager->OnLeftMouseDown(q, mEngine);
	}
}

cVoid MainWindow::OnRightMouseDown(HWND hwnd, WPARAM wParam, LPARAM lParam) {
	InputManager* manager = mEngine->GetInputManager();
	::SetCapture(hwnd);

	if (manager) {
		cPoint q;

		q.x = ((cShort) LOWORD (lParam)) - (mEngine->GetWidth() >> 1);
		q.y = ((cShort) HIWORD (lParam)) - (mEngine->GetHeight() >> 1);
		manager->OnRightMouseDown(q, mEngine);
	}
}

cVoid MainWindow::OnLeftMouseUp(HWND hwnd, WPARAM wParam, LPARAM lParam) {
	InputManager* manager = mEngine->GetInputManager();
	::ReleaseCapture();
	if (manager) {
		cPoint q;

		q.x = ((cShort) LOWORD (lParam)) - (mEngine->GetWidth() >> 1);
		q.y = ((cShort) HIWORD (lParam)) - (mEngine->GetHeight() >> 1);
		manager->OnLeftMouseUp(q, mEngine);
	}
}

cVoid MainWindow::OnRightMouseUp(HWND hwnd, WPARAM wParam, LPARAM lParam) {
	InputManager* manager = mEngine->GetInputManager();
	::ReleaseCapture();

	if (manager) {
		cPoint q;

		q.x = ((cShort) LOWORD (lParam)) - (mEngine->GetWidth() >> 1);
		q.y = ((cShort) HIWORD (lParam)) - (mEngine->GetHeight() >> 1);
		manager->OnRightMouseUp(q, mEngine);
	}
}

cVoid MainWindow::OnKeyUp(HWND hwnd, WPARAM wParam, LPARAM lParam) {

}

cVoid MainWindow::OnKeyDown(HWND hwnd, WPARAM wParam, LPARAM lParam) {

}

cVoid MainWindow::SaveScene() {
	OPENFILENAMEA open_params = {cNull};
	cChar file_name [MAX_PATH] = {cNull}; 

	open_params.lStructSize = sizeof(OPENFILENAME);
	open_params.hwndOwner = mhWnd;
	open_params.lpstrFilter = "Geometric Modeling Files (*.vmg)\0*.vmg\0\0";
	open_params.lpstrFile = file_name;
	open_params.nMaxFile = MAX_PATH;
	open_params.lpstrInitialDir = cNull;
	open_params.lpstrFileTitle = cNull;
	open_params.lpstrDefExt = "vmg";
	open_params.Flags = OFN_HIDEREADONLY | OFN_CREATEPROMPT;

	if (::GetSaveFileNameA(&open_params)) {
		XMLSerializer serializer;
		if (!serializer.SaveFile(open_params.lpstrFile, mEngine->mScene)) {
			 MessageBoxA(mhWnd, "Couldn't save Scene properly!", "Error", 0);
		}
	}
}

cVoid MainWindow::LoadScene() {
	OPENFILENAMEA open_params = {cNull};
	cChar file_name [MAX_PATH] = {cNull}; 

	open_params.lStructSize = sizeof(OPENFILENAME);
	open_params.hwndOwner = mhWnd;
	open_params.lpstrFilter = "Geometric Modeling Files (*.vmg)\0*.vmg\0\0";
	open_params.lpstrFile = file_name;
	open_params.nMaxFile = MAX_PATH;
	open_params.lpstrInitialDir = cNull;
	open_params.lpstrFileTitle = cNull;
	open_params.lpstrDefExt = "vmg";
	open_params.Flags = OFN_HIDEREADONLY | OFN_CREATEPROMPT;

	if (::GetOpenFileNameA(&open_params)) {
		XMLSerializer serializer;
		serializer.LoadFile(open_params.lpstrFile) ;
		serializer.IterateObjects();
		Object3D* obj = cNull;
		while ((obj = serializer.NextObject3D())) {
			mEngine->mScene->AddObject(obj);
			if (!strcmp(obj->GetName(), "BSplineSurface")) {
				OnAddObjectMenu(ID_ADDOBJECT_B40100, obj);
			} else if (!strcmp(obj->GetName(), "BezierSurface")) {
				OnAddObjectMenu(ID_ADDOBJECT_BEZIERSURFACE, obj);
			}
		}
		mEngine->mScene->SetWorldMatrix(serializer.GetWorldMatrix());
	}
}